/* sha512-armv7-neon.S  -  ARM/NEON assembly implementation of SHA-512 transform
 *
 * Copyright (C) 2013 Jussi Kivilinna <jussi.kivilinna@iki.fi>
 *
 * This file is part of Libgcrypt.
 *
 * Libgcrypt is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * Libgcrypt is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#if defined(HAVE_ARM_ARCH_V6) && defined(__ARMEL__) && \
    defined(HAVE_COMPATIBLE_GCC_ARM_PLATFORM_AS) && \
    defined(HAVE_GCC_INLINE_ASM_NEON)

.text

.syntax unified
.fpu neon
.arm

/* structure of SHA512_CONTEXT */
#define hd_a 0
#define hd_b ((hd_a) + 8)
#define hd_c ((hd_b) + 8)
#define hd_d ((hd_c) + 8)
#define hd_e ((hd_d) + 8)
#define hd_f ((hd_e) + 8)
#define hd_g ((hd_f) + 8)

/* register macros */
#define RK r2

#define RA d0
#define RB d1
#define RC d2
#define RD d3
#define RE d4
#define RF d5
#define RG d6
#define RH d7

#define RT0 d8
#define RT1 d9
#define RT2 d10
#define RT3 d11
#define RT4 d12
#define RT5 d13
#define RT6 d14
#define RT7 d15

#define RT01q q4
#define RT23q q5
#define RT45q q6
#define RT67q q7

#define RW0 d16
#define RW1 d17
#define RW2 d18
#define RW3 d19
#define RW4 d20
#define RW5 d21
#define RW6 d22
#define RW7 d23
#define RW8 d24
#define RW9 d25
#define RW10 d26
#define RW11 d27
#define RW12 d28
#define RW13 d29
#define RW14 d30
#define RW15 d31

#define RW01q q8
#define RW23q q9
#define RW45q q10
#define RW67q q11
#define RW89q q12
#define RW1011q q13
#define RW1213q q14
#define RW1415q q15

#define CLEAR_REG(reg) vmov.i8 reg, #0;

/***********************************************************************
 * ARM assembly implementation of sha512 transform
 ***********************************************************************/
#define rounds2_0_63(ra, rb, rc, rd, re, rf, rg, rh, rw0, rw1, rw01q, rw2, rw23q, rw1415q, rw9, rw10, interleave_op, arg1) \
	/* t1 = h + Sum1 (e) + Ch (e, f, g) + k[t] + w[t]; */ \
	vshr.u64 RT2, re, #14; \
	vshl.u64 RT3, re, #64 - 14; \
	interleave_op(arg1); \
	vshr.u64 RT4, re, #18; \
	vshl.u64 RT5, re, #64 - 18; \
	vld1.64 {RT0}, [RK]!; \
	veor.64 RT23q, RT23q, RT45q; \
	vshr.u64 RT4, re, #41; \
	vshl.u64 RT5, re, #64 - 41; \
	vadd.u64 RT0, RT0, rw0; \
	veor.64 RT23q, RT23q, RT45q; \
	vmov.64 RT7, re; \
	veor.64 RT1, RT2, RT3; \
	vbsl.64 RT7, rf, rg; \
	\
	vadd.u64 RT1, RT1, rh; \
	vshr.u64 RT2, ra, #28; \
	vshl.u64 RT3, ra, #64 - 28; \
	vadd.u64 RT1, RT1, RT0; \
	vshr.u64 RT4, ra, #34; \
	vshl.u64 RT5, ra, #64 - 34; \
	vadd.u64 RT1, RT1, RT7; \
	\
	/* h = Sum0 (a) + Maj (a, b, c); */ \
	veor.64 RT23q, RT23q, RT45q; \
	vshr.u64 RT4, ra, #39; \
	vshl.u64 RT5, ra, #64 - 39; \
	veor.64 RT0, ra, rb; \
	veor.64 RT23q, RT23q, RT45q; \
	vbsl.64 RT0, rc, rb; \
	vadd.u64 rd, rd, RT1; /* d+=t1; */ \
	veor.64 rh, RT2, RT3; \
	\
	/* t1 = g + Sum1 (d) + Ch (d, e, f) + k[t] + w[t]; */ \
	vshr.u64 RT2, rd, #14; \
	vshl.u64 RT3, rd, #64 - 14; \
	vadd.u64 rh, rh, RT0; \
	vshr.u64 RT4, rd, #18; \
	vshl.u64 RT5, rd, #64 - 18; \
	vadd.u64 rh, rh, RT1; /* h+=t1; */ \
	vld1.64 {RT0}, [RK]!; \
	veor.64 RT23q, RT23q, RT45q; \
	vshr.u64 RT4, rd, #41; \
	vshl.u64 RT5, rd, #64 - 41; \
	vadd.u64 RT0, RT0, rw1; \
	veor.64 RT23q, RT23q, RT45q; \
	vmov.64 RT7, rd; \
	veor.64 RT1, RT2, RT3; \
	vbsl.64 RT7, re, rf; \
	\
	vadd.u64 RT1, RT1, rg; \
	vshr.u64 RT2, rh, #28; \
	vshl.u64 RT3, rh, #64 - 28; \
	vadd.u64 RT1, RT1, RT0; \
	vshr.u64 RT4, rh, #34; \
	vshl.u64 RT5, rh, #64 - 34; \
	vadd.u64 RT1, RT1, RT7; \
	\
	/* g = Sum0 (h) + Maj (h, a, b); */ \
	veor.64 RT23q, RT23q, RT45q; \
	vshr.u64 RT4, rh, #39; \
	vshl.u64 RT5, rh, #64 - 39; \
	veor.64 RT0, rh, ra; \
	veor.64 RT23q, RT23q, RT45q; \
	vbsl.64 RT0, rb, ra; \
	vadd.u64 rc, rc, RT1; /* c+=t1; */ \
	veor.64 rg, RT2, RT3; \
	\
	/* w[0] += S1 (w[14]) + w[9] + S0 (w[1]); */ \
	/* w[1] += S1 (w[15]) + w[10] + S0 (w[2]); */ \
	\
	/**** S0(w[1:2]) */ \
	\
	/* w[0:1] += w[9:10] */ \
	/* RT23q = rw1:rw2 */ \
	vext.u64 RT23q, rw01q, rw23q, #1; \
	vadd.u64 rw0, rw9; \
	vadd.u64 rg, rg, RT0; \
	vadd.u64 rw1, rw10;\
	vadd.u64 rg, rg, RT1; /* g+=t1; */ \
	\
	vshr.u64 RT45q, RT23q, #1; \
	vshl.u64 RT67q, RT23q, #64 - 1; \
	vshr.u64 RT01q, RT23q, #8; \
	veor.u64 RT45q, RT45q, RT67q; \
	vshl.u64 RT67q, RT23q, #64 - 8; \
	veor.u64 RT45q, RT45q, RT01q; \
	vshr.u64 RT01q, RT23q, #7; \
	veor.u64 RT45q, RT45q, RT67q; \
	\
	/**** S1(w[14:15]) */ \
	vshr.u64 RT23q, rw1415q, #6; \
	veor.u64 RT01q, RT01q, RT45q; \
	vshr.u64 RT45q, rw1415q, #19; \
	vshl.u64 RT67q, rw1415q, #64 - 19; \
	veor.u64 RT23q, RT23q, RT45q; \
	vshr.u64 RT45q, rw1415q, #61; \
	veor.u64 RT23q, RT23q, RT67q; \
	vshl.u64 RT67q, rw1415q, #64 - 61; \
	veor.u64 RT23q, RT23q, RT45q; \
	vadd.u64 rw01q, RT01q; /* w[0:1] += S(w[1:2]) */ \
	veor.u64 RT01q, RT23q, RT67q;
#define vadd_RT01q(rw01q) \
	/* w[0:1] += S(w[14:15]) */ \
	vadd.u64 rw01q, RT01q;

#define dummy(_) /*_*/

#define rounds2_64_79(ra, rb, rc, rd, re, rf, rg, rh, rw0, rw1, interleave_op1, arg1, interleave_op2, arg2) \
	/* t1 = h + Sum1 (e) + Ch (e, f, g) + k[t] + w[t]; */ \
	vshr.u64 RT2, re, #14; \
	vshl.u64 RT3, re, #64 - 14; \
	interleave_op1(arg1); \
	vshr.u64 RT4, re, #18; \
	vshl.u64 RT5, re, #64 - 18; \
	interleave_op2(arg2); \
	vld1.64 {RT0}, [RK]!; \
	veor.64 RT23q, RT23q, RT45q; \
	vshr.u64 RT4, re, #41; \
	vshl.u64 RT5, re, #64 - 41; \
	vadd.u64 RT0, RT0, rw0; \
	veor.64 RT23q, RT23q, RT45q; \
	vmov.64 RT7, re; \
	veor.64 RT1, RT2, RT3; \
	vbsl.64 RT7, rf, rg; \
	\
	vadd.u64 RT1, RT1, rh; \
	vshr.u64 RT2, ra, #28; \
	vshl.u64 RT3, ra, #64 - 28; \
	vadd.u64 RT1, RT1, RT0; \
	vshr.u64 RT4, ra, #34; \
	vshl.u64 RT5, ra, #64 - 34; \
	vadd.u64 RT1, RT1, RT7; \
	\
	/* h = Sum0 (a) + Maj (a, b, c); */ \
	veor.64 RT23q, RT23q, RT45q; \
	vshr.u64 RT4, ra, #39; \
	vshl.u64 RT5, ra, #64 - 39; \
	veor.64 RT0, ra, rb; \
	veor.64 RT23q, RT23q, RT45q; \
	vbsl.64 RT0, rc, rb; \
	vadd.u64 rd, rd, RT1; /* d+=t1; */ \
	veor.64 rh, RT2, RT3; \
	\
	/* t1 = g + Sum1 (d) + Ch (d, e, f) + k[t] + w[t]; */ \
	vshr.u64 RT2, rd, #14; \
	vshl.u64 RT3, rd, #64 - 14; \
	vadd.u64 rh, rh, RT0; \
	vshr.u64 RT4, rd, #18; \
	vshl.u64 RT5, rd, #64 - 18; \
	vadd.u64 rh, rh, RT1; /* h+=t1; */ \
	vld1.64 {RT0}, [RK]!; \
	veor.64 RT23q, RT23q, RT45q; \
	vshr.u64 RT4, rd, #41; \
	vshl.u64 RT5, rd, #64 - 41; \
	vadd.u64 RT0, RT0, rw1; \
	veor.64 RT23q, RT23q, RT45q; \
	vmov.64 RT7, rd; \
	veor.64 RT1, RT2, RT3; \
	vbsl.64 RT7, re, rf; \
	\
	vadd.u64 RT1, RT1, rg; \
	vshr.u64 RT2, rh, #28; \
	vshl.u64 RT3, rh, #64 - 28; \
	vadd.u64 RT1, RT1, RT0; \
	vshr.u64 RT4, rh, #34; \
	vshl.u64 RT5, rh, #64 - 34; \
	vadd.u64 RT1, RT1, RT7; \
	\
	/* g = Sum0 (h) + Maj (h, a, b); */ \
	veor.64 RT23q, RT23q, RT45q; \
	vshr.u64 RT4, rh, #39; \
	vshl.u64 RT5, rh, #64 - 39; \
	veor.64 RT0, rh, ra; \
	veor.64 RT23q, RT23q, RT45q; \
	vbsl.64 RT0, rb, ra; \
	vadd.u64 rc, rc, RT1; /* c+=t1; */ \
	veor.64 rg, RT2, RT3;
#define vadd_rg_RT0(rg) \
	vadd.u64 rg, rg, RT0;
#define vadd_rg_RT1(rg) \
	vadd.u64 rg, rg, RT1; /* g+=t1; */

.align 3
.globl _gcry_sha512_transform_armv7_neon
.type  _gcry_sha512_transform_armv7_neon,%function;

_gcry_sha512_transform_armv7_neon:
	/* Input:
	 *	r0: SHA512_CONTEXT
	 *	r1: data
	 *	r2: u64 k[] constants
	 *	r3: nblks
	 */
	push {lr};

	mov lr, #0;

	/* Load context to d0-d7 */
	vld1.64 {RA-RD}, [r0]!;
	vld1.64 {RE-RH}, [r0];
	sub r0, #(4*8);

	/* Load input to w[16], d16-d31 */
	/* NOTE: Assumes that on ARMv7 unaligned accesses are always allowed. */
	vld1.64 {RW0-RW3}, [r1]!;
	vld1.64 {RW4-RW7}, [r1]!;
	vld1.64 {RW8-RW11}, [r1]!;
	vld1.64 {RW12-RW15}, [r1]!;
#ifdef __ARMEL__
	/* byteswap */
	vrev64.8 RW01q, RW01q;
	vrev64.8 RW23q, RW23q;
	vrev64.8 RW45q, RW45q;
	vrev64.8 RW67q, RW67q;
	vrev64.8 RW89q, RW89q;
	vrev64.8 RW1011q, RW1011q;
	vrev64.8 RW1213q, RW1213q;
	vrev64.8 RW1415q, RW1415q;
#endif

	/* EABI says that d8-d15 must be preserved by callee. */
	vpush {RT0-RT7};

.Loop:
	rounds2_0_63(RA, RB, RC, RD, RE, RF, RG, RH, RW0, RW1, RW01q, RW2, RW23q, RW1415q, RW9, RW10, dummy, _);
	b .Lenter_rounds;

.Loop_rounds:
	rounds2_0_63(RA, RB, RC, RD, RE, RF, RG, RH, RW0, RW1, RW01q, RW2, RW23q, RW1415q, RW9, RW10, vadd_RT01q, RW1415q);
.Lenter_rounds:
	rounds2_0_63(RG, RH, RA, RB, RC, RD, RE, RF, RW2, RW3, RW23q, RW4, RW45q, RW01q, RW11, RW12, vadd_RT01q, RW01q);
	rounds2_0_63(RE, RF, RG, RH, RA, RB, RC, RD, RW4, RW5, RW45q, RW6, RW67q, RW23q, RW13, RW14, vadd_RT01q, RW23q);
	rounds2_0_63(RC, RD, RE, RF, RG, RH, RA, RB, RW6, RW7, RW67q, RW8, RW89q, RW45q, RW15, RW0, vadd_RT01q, RW45q);
	rounds2_0_63(RA, RB, RC, RD, RE, RF, RG, RH, RW8, RW9, RW89q, RW10, RW1011q, RW67q, RW1, RW2, vadd_RT01q, RW67q);
	rounds2_0_63(RG, RH, RA, RB, RC, RD, RE, RF, RW10, RW11, RW1011q, RW12, RW1213q, RW89q, RW3, RW4, vadd_RT01q, RW89q);
	add lr, #16;
	rounds2_0_63(RE, RF, RG, RH, RA, RB, RC, RD, RW12, RW13, RW1213q, RW14, RW1415q, RW1011q, RW5, RW6, vadd_RT01q, RW1011q);
	cmp lr, #64;
	rounds2_0_63(RC, RD, RE, RF, RG, RH, RA, RB, RW14, RW15, RW1415q, RW0, RW01q, RW1213q, RW7, RW8, vadd_RT01q, RW1213q);
	bne .Loop_rounds;

	subs r3, #1;

	rounds2_64_79(RA, RB, RC, RD, RE, RF, RG, RH, RW0, RW1, vadd_RT01q, RW1415q, dummy, _);
	rounds2_64_79(RG, RH, RA, RB, RC, RD, RE, RF, RW2, RW3, vadd_rg_RT0, RG, vadd_rg_RT1, RG);
	beq .Lhandle_tail;
	vld1.64 {RW0-RW3}, [r1]!;
	rounds2_64_79(RE, RF, RG, RH, RA, RB, RC, RD, RW4, RW5, vadd_rg_RT0, RE, vadd_rg_RT1, RE);
	rounds2_64_79(RC, RD, RE, RF, RG, RH, RA, RB, RW6, RW7, vadd_rg_RT0, RC, vadd_rg_RT1, RC);
#ifdef __ARMEL__
	vrev64.8 RW01q, RW01q;
	vrev64.8 RW23q, RW23q;
#endif
	vld1.64 {RW4-RW7}, [r1]!;
	rounds2_64_79(RA, RB, RC, RD, RE, RF, RG, RH, RW8, RW9, vadd_rg_RT0, RA, vadd_rg_RT1, RA);
	rounds2_64_79(RG, RH, RA, RB, RC, RD, RE, RF, RW10, RW11, vadd_rg_RT0, RG, vadd_rg_RT1, RG);
#ifdef __ARMEL__
	vrev64.8 RW45q, RW45q;
	vrev64.8 RW67q, RW67q;
#endif
	vld1.64 {RW8-RW11}, [r1]!;
	rounds2_64_79(RE, RF, RG, RH, RA, RB, RC, RD, RW12, RW13, vadd_rg_RT0, RE, vadd_rg_RT1, RE);
	rounds2_64_79(RC, RD, RE, RF, RG, RH, RA, RB, RW14, RW15, vadd_rg_RT0, RC, vadd_rg_RT1, RC);
#ifdef __ARMEL__
	vrev64.8 RW89q, RW89q;
	vrev64.8 RW1011q, RW1011q;
#endif
	vld1.64 {RW12-RW15}, [r1]!;
	vadd_rg_RT0(RA);
	vadd_rg_RT1(RA);

	/* Load context */
	vld1.64 {RT0-RT3}, [r0]!;
	vld1.64 {RT4-RT7}, [r0];
	sub r0, #(4*8);

#ifdef __ARMEL__
	vrev64.8 RW1213q, RW1213q;
	vrev64.8 RW1415q, RW1415q;
#endif

	vadd.u64 RA, RT0;
	vadd.u64 RB, RT1;
	vadd.u64 RC, RT2;
	vadd.u64 RD, RT3;
	vadd.u64 RE, RT4;
	vadd.u64 RF, RT5;
	vadd.u64 RG, RT6;
	vadd.u64 RH, RT7;

	/* Store the first half of context */
	vst1.64 {RA-RD}, [r0]!;
	sub RK, $(8*80);
	vst1.64 {RE-RH}, [r0]; /* Store the last half of context */
	mov lr, #0;
	sub r0, #(4*8);

	b .Loop;
.ltorg

.Lhandle_tail:
	rounds2_64_79(RE, RF, RG, RH, RA, RB, RC, RD, RW4, RW5, vadd_rg_RT0, RE, vadd_rg_RT1, RE);
	rounds2_64_79(RC, RD, RE, RF, RG, RH, RA, RB, RW6, RW7, vadd_rg_RT0, RC, vadd_rg_RT1, RC);
	rounds2_64_79(RA, RB, RC, RD, RE, RF, RG, RH, RW8, RW9, vadd_rg_RT0, RA, vadd_rg_RT1, RA);
	rounds2_64_79(RG, RH, RA, RB, RC, RD, RE, RF, RW10, RW11, vadd_rg_RT0, RG, vadd_rg_RT1, RG);
	rounds2_64_79(RE, RF, RG, RH, RA, RB, RC, RD, RW12, RW13, vadd_rg_RT0, RE, vadd_rg_RT1, RE);
	rounds2_64_79(RC, RD, RE, RF, RG, RH, RA, RB, RW14, RW15, vadd_rg_RT0, RC, vadd_rg_RT1, RC);

	/* Load context to d16-d23 */
	vld1.64 {RW0-RW3}, [r0]!;
	vadd_rg_RT0(RA);
	vld1.64 {RW4-RW7}, [r0];
	vadd_rg_RT1(RA);
	sub r0, #(4*8);

	vadd.u64 RA, RW0;
	vadd.u64 RB, RW1;
	vadd.u64 RC, RW2;
	vadd.u64 RD, RW3;
	vadd.u64 RE, RW4;
	vadd.u64 RF, RW5;
	vadd.u64 RG, RW6;
	vadd.u64 RH, RW7;

	/* Store the first half of context */
	vst1.64 {RA-RD}, [r0]!;

	/* Clear used registers */
	/* d16-d31 */
	CLEAR_REG(RW01q);
	CLEAR_REG(RW23q);
	CLEAR_REG(RW45q);
	CLEAR_REG(RW67q);
	vst1.64 {RE-RH}, [r0]; /* Store the last half of context */
	CLEAR_REG(RW89q);
	CLEAR_REG(RW1011q);
	CLEAR_REG(RW1213q);
	CLEAR_REG(RW1415q);
	/* d8-d15 */
	vpop {RT0-RT7};
	/* d0-d7 (q0-q3) */
	CLEAR_REG(q0);
	CLEAR_REG(q1);
	CLEAR_REG(q2);
	CLEAR_REG(q3);

	eor r0, r0;
	pop {pc};
.size _gcry_sha512_transform_armv7_neon,.-_gcry_sha512_transform_armv7_neon;

#endif
